package com.gitee.wsl.doc.prosemirror.model.expr

import com.gitee.wsl.doc.prosemirror.model.content.TokenStream
import com.gitee.wsl.doc.prosemirror.model.content.resolveName
import com.gitee.wsl.doc.prosemirror.model.node.NodeType

sealed interface Expr {
    data class Choice(val exprs: List<Expr>) : Expr
    data class Seq(val exprs: List<Expr>) : Expr
    data class Plus(val expr: Expr) : Expr
    data class Star(val expr: Expr) : Expr
    data class Opt(val expr: Expr) : Expr
    data class Range(val min: Int, val max: Int, val expr: Expr) : Expr
    data class Name(val value: NodeType) : Expr
}

fun TokenStream.parseExpr(): Expr {
    val exprs = mutableListOf<Expr>()
    val stream = this
    do {
        exprs.add(stream.parseExprSeq())
    } while (stream.eat("|"))
    return if (exprs.size == 1) exprs[0] else Expr.Choice(exprs)
}

fun TokenStream.parseExprSeq(): Expr {
    val exprs = mutableListOf<Expr>()
    do {
        exprs.add(parseExprSubscript())
    } while (!next.isNullOrEmpty() && next != ")" && next != "|")
    return if (exprs.size == 1) exprs[0] else Expr.Seq(exprs)
}

fun TokenStream.parseExprSubscript( ): Expr {
    val stream = this
    var expr = parseExprAtom()
    while (true) {
        expr = if (stream.eat("+")) {
            Expr.Plus(expr)
        } else if (stream.eat("*")) {
            Expr.Star(expr)
        } else if (stream.eat("?")) {
            Expr.Opt(expr)
        } else if (stream.eat("{")) {
            parseExprRange( expr)
        } else {
            break
        }
    }
    return expr
}

fun TokenStream.parseNum( ): Int {
    val stream = this
    val digitRegex = "\\D".toRegex()
    if (digitRegex.matches(stream.next!!)) {
        stream.err("Expected number, got '${stream.next}'")
    }
    val result = stream.next!!.toInt()
    stream.pos++
    return result
}

fun TokenStream.parseExprRange(expr: Expr): Expr.Range {
    val stream = this
    val min = parseNum()
    var max = min
    if (stream.eat(",")) {
        max = if (stream.next != "}") parseNum() else -1
    }
    if (!stream.eat("}")) stream.err("Unclosed braced range")
    return Expr.Range(min, max, expr)
}

fun TokenStream.parseExprAtom(): Expr {
    val stream = this
    val wordRegex = "\\W".toRegex()
    if (stream.eat("(")) {
        val expr = parseExpr()
        if (!stream.eat(")")) stream.err("Missing closing paren")
        return expr
    } else if (!wordRegex.matches(stream.next!!)) {
        val exprs = resolveName(stream, stream.next!!).map { type ->
            if (stream.inline == null) {
                stream.inline = type.isInline
            } else if (stream.inline != type.isInline) {
                stream.err("Mixing inline and block content for ${type.name}")
            }
            return@map Expr.Name(type)
        }
        stream.pos++
        return if (exprs.size == 1) exprs[0] else Expr.Choice(exprs)
    } else {
        stream.err("Unexpected token '${stream.next}'")
        throw IllegalStateException("No way we get this exception")
    }
}